#include "AG.h"
#include <iostream>

using namespace std;

AG::AG(){
	string aux = "config.txt";
	loadConfig(aux);
}

AG::AG(string file){
	loadConfig(file);
}

/**
	Inicializacion random de la población
*/
void AG::initRandomPopulation(){
	for(int i=0;i<this->nChrom;i++){
		for(int j=0;j<81;j++){
			this->vChrom[i][j] = fenotype2genotype(rand()%9,4);				
		}
	}
}

/**
	calculo de la funcion de fitness para cada individuo
*/
void AG::getFitness(){	
	this->minFitness = INT_MAX;
	for(int i=0;i<this->nChrom;i++){ 
		this->fitness[i] = functionFitness(this->vChrom[i]);
		//Saco el fitness y el indice maximo de los individuos en cada generacion
		if(this->fitness[i] < this->minFitness){ 
			this->minFitness = this->fitness[i];
			this->indMin = i;
		}
	}
}

/**
	Cruza de la población
*/
void AG::cross(){
	vector<bool> vAux;
	vAux.resize(this->nGenes);
	for(int i=0;i<this->nChrom-1;i+=2){
		if(rand()/(double)RAND_MAX < this->tCross){
			int cl = rand() % 81,cr = rand() % 81;
			if(cr > cl){			
				for(int k=cl;k<cr;k++){
					vAux = this->vChrom[i+1][k];
					this->vChrom[i+1][k] = this->vChrom[i][k];
					this->vChrom[i][k] = vAux;
				}
			}
			else{
				for(int k=cr;k<cl;k++){
					vAux = this->vChrom[i+1][k];
					this->vChrom[i+1][k] = this->vChrom[i][k];
					this->vChrom[i][k] = vAux;
				}
			}
		/*int cl = rand() % 81;
			for(int k=cl;k<81;k++){
				vAux = this->vChrom[i+1][k];
				this->vChrom[i+1][k] = this->vChrom[i][k];
				this->vChrom[i][k] = vAux;
			}*/
			/*int cl = rand() % 9;
			for(int k=cl*9;k<cl*9+9;k++){
				vAux = this->vChrom[i+1][k];
				this->vChrom[i+1][k] = this->vChrom[i][k];
				this->vChrom[i][k] = vAux;
			}*/
		}
	}
}

/**
	Mutacion de la población
*/
void AG::mutation(){
	for(int i=0;i<this->nChrom;i++){
		for(int j=0;j<81;j++){		
			if(rand()/(double)RAND_MAX < this->tMut){
				bool accept = false;
				while(!accept){
					int mutPoint = rand()%this->nGenes;
					this->vChrom[i][j][mutPoint] = !this->vChrom[i][j][mutPoint]; //solo un punto de mutacion por cromosoma.
					if(genotype2fenotype(this->vChrom[i][j]) <= 8) accept = true;
				}
			}
		}
	}
}

/**
	Seleccion de la nueva población
*/	
void AG::selectNextPopulation(){
	switch(sType){
		case 0 :	{
			roulette();
			break;
		}
		case 1 : {
			competency();
			break;
		}
		case 2 : {
			window();
			break;
		}
	}
}

/**
	Ciclo de evolución
*/
void AG::evolution(){
	cout<<"entra a evolution"<<endl;
	initRandomPopulation();
	//printChromosome(); 
	cout<<"inicializa random"<<endl;
	getFitness();
	//printFitness(); 
	cout<<"pasa get fitness"<<endl;
	int generation=0;	
	cout<<"  Sudoku "<<endl;
	cout<<"Estado inicial. "<<endl;
	//printSudoku();
	while((this->minFitness > this->desFitness) && (generation < this->maxGenerations)){
		cout<<"Generacion: "<<generation<<" ";
		selectNextPopulation();
		cross();
		mutation();
		getFitness();
		cout<<"Fitness Minimo: "<<this->minFitness<<endl;
		generation++;

	}
	if(generation < this->maxGenerations)
		cout<<"Convergencia por cumplimiento de condicion de fitness."<<endl;
	else 
		cout<<"No se ha logrado resolver el sudoku presentado."<<endl;
	printSudoku();
}

/**
	Metodo de la ruleta
*/
void AG::roulette(){
	vector<vector<vector<bool> > > parents;
	parents.resize(this->nChrom);
	double sumF = 0;		
	for(int k=0;k<this->nChrom;k++) sumF += this->fitness[k];	
	for(int l=0;l<this->nChrom;l++){
		parents[l].resize(81);
		double sumR = (rand()/(double)RAND_MAX) * sumF;
		double sumP = 0;
		int j = 0;
		while(j < 81){
			sumP = sumP + this->fitness[j];
			if(sumP < sumR) j++;
			else break;
		}
		parents[l] = this->vChrom[j];
	}
	copy(parents.begin(),parents.end(),this->vChrom.begin());
}

/**
	metodo de ventanas
*/
void AG::window(){
	vector<vector<vector<bool> > > parents;
	parents.resize(this->nChrom);
	sortFitness();
	int k;
	for(int i=0;i<this->nChrom;i++){
		parents[i].resize(81);
		for(int j=0;j<81;j++) parents[i][j].resize(this->nGenes);
		k = rand() % i;
		parents[i] = this->vChrom[this->sortIndx[k]];				
	}
	copy(parents.begin(),parents.end(),this->vChrom.begin());
}

/**
	Ordenamiento por fitness
*/
void AG::sortFitness(){
	vector<double> auxFitness = this->fitness;
	for(int i=0;i<this->nChrom-1;i++){	
		double max = auxFitness[i];
		int indMax = i;	
		for(int j=i+1;j<this->nChrom;j++){
			if(auxFitness[j]>max){
			max = auxFitness[j];
			indMax = j;			
			}
		}
		auxFitness[indMax] = auxFitness[i];
		auxFitness[i] = max;
		this->sortIndx[i] = indMax;
	}
}

/**
	Metodo de competencias
*/
void AG::competency(){
	vector<vector<vector<bool> > > parents;
	parents.resize(this->nChrom);
	for(int i=0;i<this->nChrom;i++){
		parents[i].resize(81);
		double min = INT_MAX;
		int indSel;
		for(int j=0;j<this->nComp;j++){
			int l = rand() % this->nChrom;
			if(this->fitness[l] < min){
				indSel = l;
				min = this->fitness[l];	//Se puede ver que pueden existir muchos padres iguales en la generacion.
			}	
		}
		parents[i] = this->vChrom[indSel];
	}
	copy(parents.begin(),parents.end(),this->vChrom.begin());
}

bool AG::notin(int x,vector<int>& v){
	for(unsigned int i=0;i<v.size();i++){
		if(x == v[i]) return false;
	}
	return true;
}

void AG::unique(vector<int>& v){
	vector<int> aux;	
	for(unsigned int i=0;i<v.size();i++){
		bool a = notin(v[i],aux); 
		if(a) aux.push_back(v[i]);
	}
	v = aux;
}

/**
	Carga los datos del txt
*/
void AG::loadConfig(string file){
	ifstream archi;
	archi.open(file.c_str());
	if(archi.is_open()){
		string str_line;
		char* line;
		//Cantidad de cromosoma
		getline(archi,str_line);
		getline(archi,str_line);
		line = &str_line[0];
		this->nChrom = atoi(line);
		//Genes en el cromosoma
		getline(archi,str_line);
		getline(archi,str_line);
		line = &str_line[0];
		this->nGenes = atoi(line);
		//tipo de seleccion
		getline(archi,str_line);
		getline(archi,str_line);
		line = &str_line[0];
		this->sType = atoi(line);
		//tasa de cruza
		getline(archi,str_line);
		getline(archi,str_line);
		line = &str_line[0];
		this->tCross = atof(line);
		//tasa de mutacion
		getline(archi,str_line);
		getline(archi,str_line);
		line = &str_line[0];
		this->tMut = atof(line);
		//fitness deseado
		getline(archi,str_line);
		getline(archi,str_line);
		line = &str_line[0];
		this->desFitness = atof(line);
		//numero de individuos en la competencia
		getline(archi,str_line);
		getline(archi,str_line);
		line = &str_line[0];
		this->nComp = atoi(line);
		//numero de individuos en brecha Generacional
		getline(archi,str_line);
		getline(archi,str_line);
		line = &str_line[0];
		this->G = atoi(line);
		//Cantidad maxima de generaciones
		getline(archi,str_line);
		getline(archi,str_line);
		line = &str_line[0];
		this->maxGenerations = atoi(line);
		}		
	archi.close();
	this->vChrom.resize(this->nChrom);
	this->sortIndx.resize(this->nChrom);
	this->fitness.resize(this->nChrom);
	for(int i=0;i<this->nChrom;i++){ 
		this->vChrom[i].resize(81);		
		//Determino los miembros del grupo al que pertenece
		for(int j=0;j<81;j++) this->vChrom[i][j].resize(this->nGenes);		
	}
	srand(time(NULL));
}

void AG::printChromosome(){
	for(int i=0;i<this->nChrom;i++){
		for(int j=0;j<81;j++){
			cout<<genotype2fenotype(this->vChrom[i][j])<<" ";
		}
	}
	cout<<endl;
}

void AG::printSudoku(){
	for(int i=0;i<9;i++){
		for(int j=0;j<3;j++){
			for(int k=0;k<3;k++){
				cout<<genotype2fenotype(vChrom[this->indMin][i*3+j*9+k])<<" ";				
			}
			cout<<"    ";
		}
		cout<<endl;
		if((i == 2) || (i == 5) || (i == 8)) cout<<endl;
	}
}
